package edu.tamu.cse.teamproject1.Database;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import edu.tamu.cse.teamproject1.Database.Interfaces.IColumnDefinition;
import edu.tamu.cse.teamproject1.Database.Interfaces.IDatabaseTable;
import edu.tamu.cse.teamproject1.Database.Interfaces.ITuple;
import edu.tamu.cse.teamproject1.Database.Interfaces.ITupleElement;

/**
 * The class Table allows tables to be stored in memory in a Database
 * it contains a list of columns, a list of tuples and its name
 * 
 * @author Ben Unsworth
 * @author	Christopher Weldon <chris@chrisweldon.net>
 * @since Release 0.1.0
 */
public class Table implements IDatabaseTable {
	/**
	 * Name of the table
	 */
	String name;
	List<IColumnDefinition> columns;
	List<ITuple> tuples;
	boolean isView;
	
	/**
	 * Renames the table
	 * @param tableName
	 */
	void setTableName(String tableName) {
		name = tableName;
	}
	
	/**
	 * Get the table's name
	 * @return the table name
	 */
	String getTableName() {
		return name;
	}
	
	/**
	 * Creates a new table and optionally allows the name to be defined
	 * 
	 * @param tableName
	 */
	public Table(String tableName) {
		name = tableName;
		columns = new ArrayList<IColumnDefinition>();
		tuples = new ArrayList<ITuple>();
	}
	
	/**
	 * Constructs a new table with definitions, but no data
	 * 
	 * @param tableName Name of the table
	 * @param newColumns Definition list of new columns
	 * @author Chris Weldon <chris@chrisweldon.net>
	 */
	public Table(String tableName, List<IColumnDefinition> newColumns) {
		name = tableName;
		columns = newColumns;
		tuples = new ArrayList<ITuple>();
	}
	
	/**
	 * Constructor, creates a new table and defines its
	 * name, the columns, and the tuples
	 * @param tableName
	 * @param newColumns
	 * @param newTuples
	 */
	public Table(String tableName, List<IColumnDefinition> newColumns, List<ITuple> newTuples){
		name = tableName;
		columns = newColumns;
		tuples = newTuples;
	}
	
	/**
	 * Get the column's 
	 * @return the table's columns definitions
	 */
	List<IColumnDefinition> getColumns(){
		return columns;
	}
	
	/**
	 * Returns all tuples from the table
	 * 
	 * @return List of all tuples in the table
	 */
	@Override
	public List<ITuple> getAllTuples() {
		return this.tuples;
	}

	/**
	 * Retrieves all of the column definitions on this table
	 * 
	 * @return List of all column definitions
	 */
	@Override
	public List<IColumnDefinition> getColumnLists() {
		return this.columns;
	}

	/**
	 * Returns the name of the table
	 * 
	 * @return Name of the table
	 */
	@Override
	public String getName() {
		return this.name;
	}

	/**
	 * Sets the name of the table
	 * 
	 * @param name
	 */
	@Override
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * Adds a column to the table
	 * 
	 * In addition to adding this column definition to the table, it will go
	 * through and update all tuples and create blanks of the new column to 
	 * maintain consistency. 
	 * 
	 * @param IColumnDefinition columnDef New column
	 */
	@Override
	public void addColumn(IColumnDefinition columnDef) {
		this.columns.add(columnDef);
		
		ITupleElement element = columnDef.createEmptyTupleElement();
		/*add a TupleElement to each tuple matching the column just added*/
		for (ITuple tuple : this.tuples) {
			tuple.addTupleElement(element);
		}
	}
	
	/**
	 * Deletes a tuple from the table
	 * @param tuple
	 */
	@Override
	public void deleteTuple(ITuple tuple) {
		/*loop through the table till you find the tuple to be removed*/
		for (ITuple localTuple : this.tuples) {
			if (localTuple.getUniqueId() == tuple.getUniqueId()) {
				this.tuples.remove(localTuple);
				break;
			}
		}
	}

	/**
	 * Deletes a column from the database
	 * 
	 * This method will delete the columnDefinition from the table and will also
	 * delete the tuple elements with matching column names from all tuples to 
	 * maintain consistency. 
	 * 
	 * @param columnName
	 */
	@Override
	public void deleteColumn(String columnName) {
		/*loop to and remove the columnDefinition of the name: 'columnName'*/
		for (IColumnDefinition def : this.columns) {
			if (def.getColumnName().equals(columnName)) {
				this.columns.remove(def);
				break;
			}
		}
		
		// CAUTION: O(n^2)
		/*loop through each tuple and find the tupleElement matching to
		 * the columnDefinition just removed and remove those tupleElements*/
		for (ITuple tuple : this.tuples) {
			for (ITupleElement element : tuple.getAllElements()) {
				if (element.getColumnName().equals(columnName)) {
					tuple.deleteTupleElement(element);
					break;
				}
			}
		}
	}

	/**
	 * Clears the tuples in this table
	 */
	@Override
	public void clearTuples() {
		this.tuples = new ArrayList<ITuple>();
	}

	/**
	 * Adds the tuple in this table
	 * @param tuple
	 * @throws IllegalArgumentException
	 */
	@Override
	public void addTuple(ITuple tuple) {
		/*search through all the tuples and match them 
		 * with the column definitions*/
		List<ITupleElement> ElemList = tuple.getAllElements();
		List<IColumnDefinition> keyList = findKeys();
		List<IColumnDefinition> duplicates = new ArrayList<IColumnDefinition>();
		List<IColumnDefinition> nonNullList = findNonNullables();
		
		/*check to make sure all tupleElements are valid in tuple*/
		for(Iterator<ITupleElement> i = ElemList.iterator(); i.hasNext();){
			ITupleElement testElem = i.next();
			String columnName = testElem.getColumnName();
			
			/*couldn't find a matching column to the tupleElement*/
			if(!compareColumn(columnName))
				throw new IllegalArgumentException("TupleElement, " + columnName + 
													", has no matching ColumnDefition");
			
			/*tuple is a duplicate*/
			if(!this.isView && findDuplicates(duplicates, columnName))
				throw new IllegalArgumentException("TupleElement is a duplicate");
			
			/*tuple should be a key*/
			if(compareToKeys(keyList, columnName) > -1){
				if(!testElem.isKey())
					throw new IllegalArgumentException("TupleElement should be a key");
				
			}
			
			/*if the tupleElement is a key make sure there are no other TupleElements
			 * of the same columnDefinition are identical */
			if(testElem.isKey()){
				if(!this.isView && compareKeyToTuples(testElem))
					throw new IllegalArgumentException("TupleElement is identical to another key");				
			}
			
			/*tuple has illegal nulls*/
			if(!this.isView && compareToNonNullables(nonNullList, columnName)){
				if(testElem.isNullable())
					throw new IllegalArgumentException("TupleElement should not be nullable");
				else if(testElem.getValue() == null)
					throw new IllegalArgumentException("TupleElement is null");
			}
			

			/*is tuple element's type equivalent to its matching column*/
			if(!compareType(testElem, columnName))
				throw new IllegalArgumentException
				("TupleElement's type does not match Column Definition");
			
			duplicates.add(getColumnByName(columnName));
		}
		
		/*add the tuple, all tupleElements are valid*/
		tuples.add(tuple);
	}
	
	/**
	 * Compare's the type of the test element with the column name
	 * @param testElem
	 * @param columnName
	 * @return true if the tupleElement's and the ColumnDefinition's types agree, false otherwise 
	 */
	boolean compareType(ITupleElement testElem, String columnName){
		IColumnDefinition column = getColumnByName(columnName);

		if(column.getColumnType() == testElem.getColumnType()) return true;
		else return false;
	}
	
	
	/**
	 * Finds the columns that are keys from the columns list.
	 * @return keyList
	 */
	List<IColumnDefinition> findKeys(){
		List<IColumnDefinition> keyList = new ArrayList<IColumnDefinition>();
		IColumnDefinition column;
		
		/*loop through each columnDefinition and find the ones designated as keys*/
		for(Iterator<IColumnDefinition> i = columns.iterator(); i.hasNext();){
			column = i.next();
			if(column.isKey()) keyList.add(column);
		}
		return keyList;
	}
	
	/**
	 * Determines whether a column is a key or not
	 * @param keyList
	 * @param columnName
	 * @return i, the index of the list of keys that matches the columnName, if -1
	 * there were none
	 */
	int compareToKeys(List<IColumnDefinition> keyList, String columnName){
		for(int i = 0; i < keyList.size(); i++){
			if(keyList.get(i).getColumnName().equals(columnName)) return i;
		}
		
		return -1;
	}
	
	/**
	 * Compares they key to the tuples and checks if there are duplicates
	 * @param testElem
	 * @return true if they are duplicates, false if not
	 */
	boolean compareKeyToTuples(ITupleElement testElem){
		ITuple tuple;
		ITupleElement tupleElem;
		
		//loops through each tuple
		for(Iterator<ITuple> i = tuples.iterator(); i.hasNext();){
			tuple = i.next();
			
			//loops through each tupleElement
			for(Iterator<ITupleElement> j = tuple.getAllElements().iterator(); j.hasNext();){
				tupleElem = j.next();
				
				//compare to make sure the two tupleElements are not identical
				if(tupleElem.getColumnName().equals(testElem.getColumnName())){
					if(tupleElem.getValue().equals(testElem.getValue())) return true;
				}
			}
		}
		return false;
	}
	
	/**
	 * Checks to see if the given name matches in any of the defined columns
	 * @param columnName
	 * @return true if tupleElement's columnName was found, false if not
	 */
	boolean compareColumn(String columnName){
		IColumnDefinition column;
		
		/*loop through each column and return true if one is found matching
		 *  the TupleElement*/
		for(Iterator<IColumnDefinition> i = columns.iterator(); i.hasNext();){
			column = i.next();
			if(column.getColumnName().equals(columnName)) return true;
		}
		return false;
	}
	
	/**
	 * Returns a column that matches the name provided
	 * @param columnName
	 * @return column
	 */
	IColumnDefinition getColumnByName(String columnName){
		IColumnDefinition column;
		
		/*loop through the columns and return the column with the name: columnName*/
		for(Iterator<IColumnDefinition> i = columns.iterator(); i.hasNext();){
			column = i.next();
			if(column.getColumnName().equals(columnName)) return column;
		}
		throw new IllegalArgumentException("Column not found");
	}
	
	/**
	 * Finds all nonNullable columns in the column list
	 * @return NonNullList
	 */
	List<IColumnDefinition> findNonNullables(){
		List<IColumnDefinition> NonNullList = new ArrayList<IColumnDefinition>();
		IColumnDefinition column;
		
		/*loop through the columnDefinitions and add each column that is non-nullable*/
		for(Iterator<IColumnDefinition> i = columns.iterator(); i.hasNext();){
			column = i.next();
			if(!column.isNullable()) NonNullList.add(column);
		}
		return NonNullList;
	}
	
	/**
	 * Determines if a column should be nonNullable
	 * @param nonNullList
	 * @param columnName
	 * @return return if the column should or is nonNullable
	 */
	boolean compareToNonNullables(List<IColumnDefinition> nonNullList, String columnName){
		IColumnDefinition column;
		
		/*determine if the columnName from a TupleElement is a non-nullable tupleElement*/
		for(Iterator<IColumnDefinition> i = nonNullList.iterator(); i.hasNext();){
			column = i.next();
			if(column.getColumnName().equals(columnName)) return true;
		}
		return false;
	}
	
	/**
	 * Compares the columnName to duplicates column names to check whether
	 * there it is a duplicate of one of the elements in the list
	 * @param duplicates, columnName
	 * @return whether columnName is a duplicate of the List of duplicates
	 */
	boolean findDuplicates(List<IColumnDefinition> duplicates, String columnName){
		IColumnDefinition duplicate;
		
		/*determine if this tupleElement's columnDefinition has already been used*/
		for(Iterator<IColumnDefinition> i = duplicates.iterator(); i.hasNext();){
			duplicate = i.next();
			if(duplicate.getColumnName().equals(columnName)) return true;
		}
		return false;
	}
	
	/**
	 * Defines the cloning method
	 */
	@Override
	public Object clone()
	{
		try {
			return super.clone();
		} catch (CloneNotSupportedException e) {
			return null;
		}
	}

	
	/**
	 * Returns an array of strings which contains all of the column names in a table
	 * @return ColumnNames
	 */
	@Override
	public String[] getTableColumnNames() {
		String[] ColumnNames = new String[columns.size()];
		int i = 0;
		for(IColumnDefinition c: this.columns){
			ColumnNames[i] = c.getColumnName();
			i++;
		}
		return ColumnNames;
	}

	
	/**
	 * Return a 2D array of all the table's tuples
	 * each row is a new tuple, each column is a TupleElement in the tuple
	 */
	@Override
	public Object[][] getTableTuples() {
		Object[][] tupleList = new Object[tuples.size()][columns.size()];
		/*moves through each tuple to add their tupleElements to the 2d array*/
		int i = 0;
		for(ITuple t: this.tuples){
			/*moves through each column and matches the tupleElement to it*/
			int j = 0;
			for(IColumnDefinition c: this.columns){
				String columnName = c.getColumnName();
				/*sets whether there is a value or not for this column in this tuple*/
				boolean isNull = true;
				for(ITupleElement te: t.getAllElements()){
					if(columnName.equals(te.getColumnName())){
						/*there is a value for this column*/
						isNull = false;
						/*add value to 2d array*/
						tupleList[i][j] = te.getValue();
						break;
					}
				}
				/*check whether there can be a null value for this column*/
				if(!c.isNullable() && isNull == true){
					throw new IllegalStateException("TupleElement null when ColumnDef requires value");
				}else if(isNull == true){
					tupleList[i][j] = null;
				}
				j++;
			}
			i++;
		}
		
		return tupleList;
	}

	/**
	 * Sets a status indicator whether or not this table is a view.
	 */
	@Override
	public void setIsView(boolean isView) {
		this.isView = isView;
	}
}
